#pragma once
#include <mutex>
#include "DataFilter.h"

class Cache {
public:
	/**
	* @note add a queue to avoid asynchrony issues(write-read at the same time), mainly happens in embed device.
	*/
	static void recvModels(const CVector<ModelArg>& modelArgs) {
		if (!s_isReceivingDataEnabled) return;
		std::lock_guard<std::mutex> lock(s_modelMutex);
		s_curTmpModelArgs = modelArgs;
		s_isNewModelsRecved = true;
	}
	static void recvLines(const CVector<LineArg>& lineArgs) {
		if (!s_isReceivingDataEnabled) return;
		std::lock_guard<std::mutex> lock(s_lineMutex);
		s_curTmpLineArgs = lineArgs;
		s_isNewLinesRecved = true;
	}

	static bool isReceivingData() { return s_isReceivingDataEnabled; }
	static void setReceivingData(bool enabled) { s_isReceivingDataEnabled = enabled; }

	// call per frame
	static bool useNewlyRcvdModels() {
		s_modelMutex.lock();
		if (!s_isNewModelsRecved) {
			s_modelMutex.unlock();
			return false;
		}
		s_isNewModelsRecved = false;
		auto _modelArgs = s_curTmpModelArgs;
		s_modelMutex.unlock();

		setSizeForModels(_modelArgs);
		if (Proj::isSSQK())
			adjustModelForSSQK(_modelArgs);

		bool hasNew = false;
		for (const auto& modelArg : _modelArgs) {
			if (!DataFilter::get()->checkValid(modelArg))
				continue;
			if (ModelType::Unknown != modelArg.type)
				s_modelArgMap.insert(modelArg.ID, modelArg);
			else {// unknown model type
				auto arg = modelArg;
				arg.type = ModelType::Undefined;
				s_modelArgMap.insert(arg.ID, arg);
			}
			hasNew = true;
		}
		for (const auto& kv : s_modelArgMap) {//@note const kv means kv's ref can't change, but value can!
			auto& arg = kv.value;
			++arg.updateCnt;
		}
		return hasNew;
	}

	// call per frame
	static bool useNewlyRcvdLines() {
		s_lineMutex.lock();
		if (!s_isNewLinesRecved) {
			s_lineMutex.unlock();
			return false;
		}
		s_isNewLinesRecved = false;
		auto _lineArgs = s_curTmpLineArgs;
		s_lineMutex.unlock();

		CVector<LineArg> validLines;
		for (auto& lineArg : _lineArgs) {
			if (!DataFilter::get()->checkValid(lineArg))
				continue;
			validLines << lineArg;
		}

		setTagsForValidLines(validLines);

		bool hasNew = !validLines.empty();
		for (auto& lineArg : validLines)
			s_lineArgMap.insert(lineArg.tag, lineArg);

		for (const auto& kv: s_lineArgMap) {// kv value can modify, kv's ref can't!
			auto& arg = kv.value;
			++arg.updateCnt;
		}
		return hasNew;
	}

	static void startLoadingZhicheAtNewThread(int delayMs = 0);

	static const ZhicheArg& zhicheArg() { return s_zhicheArg; }
	static void updateZhicheSpeed(float speed) { s_zhicheArg.speed = speed; }
	static void updateZhicheAngle(float angle) {
		if (s_zhicheArg.angle == angle)
			return;
		s_zhicheArg.angle = angle;
		s_isZhicheDirty = true;
	}
	static bool isZhicheDirty() {
		bool isDirty = s_isZhicheDirty;
		s_isZhicheDirty = false;
		return isDirty;
	}

	static void adjustModelForSSQK(CVector<ModelArg>& modelArgs);

	/**
	* @warning for QMap or std::map, if using subscripts and no value, the key with default value will be inserted into the map.
	* @example visit non-exists key1,key2: auto v1 = map[key1]; auto v2 = map[key2]; then map.size() == 2.
	*/
	inline static const CMap<LineTag, LineArg>& lineArgMap() { return s_lineArgMap; }
	inline static const LineArg& lineArg(LineTag tag) { if (s_lineArgMap.contains(tag)) return s_lineArgMap[tag]; return s_defLineArg; }
	inline static void increaseUpdateFrameCnt(LineTag tag) { if (s_lineArgMap.contains(tag)) ++s_lineArgMap[tag].updateFrameCnt; }
	inline static void removeLine(LineTag tag) { s_lineArgMap.remove(tag); }
	inline static bool lineExists(LineTag tag) { return s_lineArgMap.contains(tag); }
	inline static int validLineNum() { return s_lineArgMap.size(); }

	inline static const CMap<int, ModelArg>& modelArgMap() { return s_modelArgMap; }
	inline static const ModelArg& modelArg(int ID) { if (s_modelArgMap.contains(ID)) return s_modelArgMap[ID]; return s_defModelArg; }
	inline static ModelArg& modelArgRef(int ID) { if (s_modelArgMap.contains(ID)) return s_modelArgMap[ID]; return s_defModelArg; }
	inline static void increaseUpdateFrameCnt(int ID) { if (s_modelArgMap.contains(ID)) ++s_modelArgMap[ID].updateFrameCnt; }
	inline static void removeModel(int ID) { s_modelArgMap.remove(ID); }

	static bool isALCReady() {
		ALC alc = s_status.alc;
		bool leftOK = Enum_leftExists(alc) && lineExists(LineTag::L);
		if (leftOK) return true;
		bool rightOK = Enum_rightExists(alc) && lineExists(LineTag::R);
		return rightOK;
	}

	static bool isLDWReady() {
		LDW ldw = s_status.ldw;
		bool leftOK = ldw == LDW::From_Left_Warning && lineExists(LineTag::L);
		if (leftOK) return true;
		bool rightOK = ldw == LDW::From_Right_Warning && lineExists(LineTag::R);
		return rightOK;
	}

	static bool isDCLCReady() {
		DCLC dclc = s_status.dclc;
		bool midOK = dclc == DCLC::Mid && lineExists(LineTag::L) && lineExists(LineTag::R);
		if (midOK) return true;
		bool leftOK = dclc == DCLC::ToLeft && lineExists(LineTag::L) && lineExists(LineTag::L2);
		if (leftOK) return true;
		bool rightOK = dclc == DCLC::ToRight && lineExists(LineTag::R) && lineExists(LineTag::R2);
		return rightOK;
	}

	static bool isSQ_ALCReady() {
		sq::ALC alc = s_sqStatus.alc;
		bool leftOK = (sq::ALC::Left == alc) && lineExists(LineTag::L) && lineExists(LineTag::L2);
		if (leftOK) return true;
		bool rightOK = (sq::ALC::Right == alc) && lineExists(LineTag::R) && lineExists(LineTag::R2);
		return rightOK;
	}

	static int getFrontModelIdClosestToOrigin();
	static CVector<int> getRangeModelIDs(float hRadius, float vRadius, HDir towards);

	inline static void updateDCLC(DCLC dclc) { s_status.dclc = dclc; };
	inline static void updateALC(ALC alc) { s_status.alc = alc; };
	inline static void updateLDW(LDW ldw) { s_status.ldw = ldw; };
	inline static void updateAccTG(ACC_TG accTG) { s_status.accTG = accTG; }
	inline static void updateTailLamp(TailLamp tailLamp) { s_status.tailLamp = tailLamp; }

	static void updateStatus(const Status& status) {
		updateDCLC(status.dclc);
		updateALC(status.alc);
		updateLDW(status.ldw);
		updateAccTG(status.accTG);
		updateTailLamp(status.tailLamp);
	}

	inline static const Status& status() { return s_status; }
	inline static void clearStatus() { s_status = {}; }

	inline static void updateSQ_ALC(sq::ALC alc) { s_sqStatus.alc = alc; };
	inline static void updateSQ_ACC(sq::ACC acc) { s_sqStatus.acc = acc; };
	inline static void updateSQ_FCW(sq::FCW fcw) { s_sqStatus.fcw = fcw; };
	inline static void updateSQ_ACAS(sq::ACAS acas) { s_sqStatus.acas = acas; };
	inline static void updateSQ_NOA(sq::NOA noa) { s_sqStatus.noa = noa; };

	inline static void updateSQStatus(const sq::Status& status) {
		updateSQ_ALC(status.alc);
		updateSQ_ACC(status.acc);
		updateSQ_FCW(status.fcw);
		updateSQ_ACAS(status.acas);
		updateSQ_NOA(status.noa);
	}

	inline static const sq::Status& sqStatus() { return s_sqStatus; }
	inline static void clearSQStatus() { s_sqStatus = {}; }

private:
	Cache() = default;

	static void setSizeForModels(CVector<ModelArg>& modelArgs);

	static void setTagsForValidLines(CVector<LineArg>& lines);

	static std::mutex s_modelMutex, s_lineMutex;
	static CVector<ModelArg> s_curTmpModelArgs;
	static CVector<LineArg> s_curTmpLineArgs;
	static bool s_isReceivingDataEnabled;

	static LineArg s_defLineArg;
	static ModelArg s_defModelArg;
	static ZhicheArg s_zhicheArg;

	static CMap<LineTag, LineArg> s_lineArgMap;
	static CMap<int, ModelArg> s_modelArgMap;
	static Status s_status;
	static sq::Status s_sqStatus;
	
	static bool s_isZhicheDirty;
	static bool s_isNewLinesRecved, s_isNewModelsRecved;
};